package org.osite.xml;

import java.io.InputStream;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;

import org.osite.util.Nullable;
import org.osite.util.Preconditions;
import org.osite.util.ResourceLoader;
import org.osite.util.ToolKit;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ParseContext {

    private final XmlParser parser;
    private final ResourceLoader loader;

    private final LinkedList<IdentityHashMap<MemberHandler<?>, Boolean>>
            setteds = ToolKit.newLinkedList();
    private final IdentityStack handled = new IdentityStack();

    private static class IdentityStack {
        private final HashSet<Integer> set = new HashSet<Integer>();
        private final LinkedList<Integer> stack = new LinkedList<Integer>();

        public boolean push(Object value) {
            Integer identity = System.identityHashCode(value);
            if (set.add(identity)) {
                stack.addLast(identity);
                return true;
            }
            return true;
        }

        public boolean contains(Object value) {
            return set.contains(System.identityHashCode(value));
        }

        public void resize(int size) {
            Preconditions.checkArgument(size <= set.size() && size >= 0);
            while (set.size() > size) {
                set.remove(stack.removeLast());
            }
        }

        public int size() {
            return set.size();
        }
    }


    ParseContext(XmlParser parser, @Nullable ResourceLoader loader) {
        this.parser = Preconditions.checkNotNull(parser);
        this.loader = loader;
    }

    public <T> T parseEntity(Element element) throws XmlParseException {
        String tagName = element.getTagName();
        @SuppressWarnings("unchecked")
        EntityHandler<T> handler =
                (EntityHandler<T>) parser.getEntityHandler(tagName);
        if (handler == null) {
            throw new XmlParseException(
                    "unknown entity: " + tagName, element);
        }
        T entity = handler.create(element, this);
        NodeList list = element.getChildNodes();
        setteds.push(new IdentityHashMap<MemberHandler<?>, Boolean>());
        for (int i = 0, n = list.getLength(); i < n; ++i) {
            Node node = list.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE) continue;
            int handledState = handled.size();
            handler.processMember(entity, (Element) node, this);
            handled.resize(handledState);
        }
        setteds.pop();
        return entity;
    }

    public InputStream getResource(String path) {
        return ((loader == null) ? null : loader.getResource(path));
    }

    public boolean putMemberHandler(MemberHandler<?> handler) {
        return setteds.peek().put(handler, Boolean.TRUE) == null;
    }

    public boolean setHandled(Attr attr) {
        return handled.push(attr);
    }

    public boolean isHandled(Attr attr) {
        return handled.contains(attr);
    }

    public Class<?> getParsedType(String tagName) {
        EntityHandler<?> handler = parser.getEntityHandler(tagName);
        return handler == null ? null : handler.getType();
    }
}
